Skip to content

Support non-unique keys for dictionary column types - #22839

Merged
rapids-bot[bot] merged 43 commits into
NVIDIA:mainfrom
davidwendt:dict-non-unique-keys
Jul 31, 2026
Merged

Support non-unique keys for dictionary column types#22839
rapids-bot[bot] merged 43 commits into
NVIDIA:mainfrom
davidwendt:dict-non-unique-keys

Conversation

@davidwendt

@davidwendt davidwendt commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Description

Updates libcudf DICTIONARY columns to support non-unique keys.
Duplicate keys are support on input for all libcudf APIs but any APIs that return dictionary columns will likely have non-unique keys. The exception to this is the make_dictionary_column which does not inspect the keys, indices it is given and the cudf::dictionary::set_keys() which always honors the given keys in uniqueness (or not) and order.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

@davidwendt davidwendt self-assigned this Jun 10, 2026
@davidwendt davidwendt added 2 - In Progress Currently a work in progress libcudf Affects libcudf (C++/CUDA) code. improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels Jun 10, 2026
@copy-pr-bot

copy-pr-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@davidwendt

Copy link
Copy Markdown
Contributor Author

/ok to test

@davidwendt

Copy link
Copy Markdown
Contributor Author

/ok to test

@pmattione-nvidia

Copy link
Copy Markdown
Contributor

are there any existing kernels that assume dictionaries have unique keys?

@davidwendt

Copy link
Copy Markdown
Contributor Author

are there any existing kernels that assume dictionaries have unique keys?

I've not found any as of yet. Still working on this.

@davidwendt davidwendt added 3 - Ready for Review Ready for review by team and removed 2 - In Progress Currently a work in progress labels Jul 1, 2026
@davidwendt

Copy link
Copy Markdown
Contributor Author

/ok to test

@davidwendt

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Dictionary documentation now allows duplicate keys. Argmin/argmax and minmax reductions now operate on dictionary keys directly via lazy iterators and keys-type dispatch. Tests were added for duplicate-key behavior across dictionary APIs and reductions.

Changes

Dictionary duplicate-keys support

Layer / File(s) Summary
Documentation updates
cpp/include/cudf/dictionary/dictionary_column_view.hpp, cpp/include/cudf/dictionary/dictionary_factories.hpp, cpp/include/cudf/dictionary/update_keys.hpp, cpp/doxygen/developer_guide/DEVELOPER_GUIDE.md
Dictionary docs now describe keys as non-unique, remove prior uniqueness and ordering assumptions, and add a note about duplicate keys in set_keys.
Dictionary argmin/argmax iterator logic
cpp/src/reductions/argmax.cu, cpp/src/reductions/argmin.cu, cpp/src/reductions/extrema_utils.cuh
Argmin and argmax now dispatch on dictionary key types, and extrema utilities compute dictionary extrema directly from lazy iterators with null-aware handling.
Dictionary minmax reduction rework
cpp/src/reductions/minmax.cu
Minmax reductions now dispatch on dictionary key types, reduce dictionary keys through lazy iterators, and return scalar results through a dictionary-specific path.
Dictionary API duplicate-key tests
cpp/tests/dictionary/add_keys_test.cpp, cpp/tests/dictionary/factories_test.cpp, cpp/tests/dictionary/gather_test.cpp, cpp/tests/dictionary/remove_keys_test.cpp, cpp/tests/dictionary/set_keys_test.cpp, cpp/tests/dictionary/sort_test.cpp
Duplicate-key tests were added for add_keys, factories, gather, remove_keys, set_keys, and sort, with decoded-output checks and key-count assertions where applicable.
Dictionary reduction tests with unsorted duplicate keys
cpp/tests/reductions/reduction_tests.cpp
Reduction tests now validate MIN/MAX, cudf::minmax, and ARGMIN/ARGMAX for dictionary keys that are unsorted and duplicated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested labels: feature request
Suggested reviewers: bdice, vyasr, mroeschke

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: allowing non-unique dictionary keys.
Description check ✅ Passed The description is directly related to the dictionary non-unique key support changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@GregoryKimball GregoryKimball moved this from Burndown to Slip in libcudf Jul 20, 2026
@GregoryKimball GregoryKimball moved this from Slip to Burndown in libcudf Jul 20, 2026
@davidwendt davidwendt moved this from Burndown to Slip in libcudf Jul 21, 2026

@mhaseeb123 mhaseeb123 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving with minor nits

Comment thread cpp/src/reductions/minmax.cu Outdated
Comment thread cpp/src/reductions/minmax.cu Outdated
Comment thread cpp/src/reductions/minmax.cu Outdated
Comment on lines +194 to +195
auto minimum = make_fixed_width_scalar(key_type, stream, mr);
auto maximum = make_fixed_width_scalar(key_type, stream, mr);

@mhaseeb123 mhaseeb123 Jul 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude flagged that make_fixed_width_scalar(data_type, ...) goes through scalar_construction_helper, which constructs with is_valid = false.

Also ref: Copilot's concern about decimal scale being dropped seems unhandled

Suggested change
auto minimum = make_fixed_width_scalar(key_type, stream, mr);
auto maximum = make_fixed_width_scalar(key_type, stream, mr);
auto make_scalar = [&]() -> std::unique_ptr<scalar> {
if constexpr (cudf::is_fixed_point<T>()) {
return make_fixed_point_scalar<T>(
storage_type{}, numeric::scale_type{key_type.scale()}, stream, mr);
} else {
auto scalar = make_fixed_width_scalar(key_type, stream, mr);
scalar->set_valid_async(true, stream);
return std::move(scalar);
}
};
auto minimum = make_scalar();
auto maximum = make_scalar();

Please also assert validity in the new tests — DictionaryReductionTest.MinMaxUnsortedDuplicateKeys only compares .first / ->value(), and no pre-existing gtest checks is_valid() for a fixed-width dictionary min/max, which is why CI is green on this. DictionaryTypes is also only {int32_t, int64_t, float, double}, so a decimal case would be needed to pin the scale.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decimal scale being dropped is a separate issue in this source code (not just dictionary) which I will address in a follow-on PR. The is_valid case is valid and I'll fix that here.

Comment thread cpp/tests/reductions/reduction_tests.cpp
Comment thread cpp/src/reductions/minmax.cu
auto d_dictionary = column_device_view::create(col, stream);
if (col.has_nulls()) {
auto pair_to_minmax = cuda::make_transform_iterator(
cudf::dictionary::detail::make_dictionary_pair_iterator<T>(*d_dictionary, true),

@mhaseeb123 mhaseeb123 Jul 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up PR: Perhaps we can we add a benchmark, if none exists, to measure whether loading keys[indices[i]] per row rather than reducing indices and slicing keys affects low-cardinality dictionary minmax performance?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, some exploration would be good here. This could first do a groupby-count to determine which keys are present (possibly including null values) and then do the minmax reduction over the keys with nonzero counts.

Comment thread cpp/src/reductions/argmin.cu Outdated
Comment thread cpp/tests/reductions/reduction_tests.cpp

@bdice bdice left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving with minor comments, but we need the null test cases and other pieces that @mhaseeb123 mentioned.

Comment thread cpp/include/cudf/dictionary/dictionary_column_view.hpp Outdated
Comment on lines +124 to +125
* Although duplicate keys are allowed, indices in the returned dictionary may
* only reference one of the duplicates.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read this 3 times before I started to understand it.

What is guaranteed/preserved here? I think the number of keys in the output dictionary is keys.size(), even if keys contains duplicates. But because we have to reassign all the indices to match the new keys, we arbitrarily choose one of those duplicate values to provide the index for reassignment. Do we (or should we) provide any guarantee around which of the duplicates is used for the index? First/last/any?

It is hard to provide determinism and lossless conversion/transformation with the ambiguities introduced by duplicate keys.

Maybe there's a better way to say this, or maybe the current state is fine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We introduced non-determinism by no longer requiring sorted keys.
So determinism would be a big requirement and we would have reconsider all of the current dictionary implementation in libcudf again.

auto d_dictionary = column_device_view::create(col, stream);
if (col.has_nulls()) {
auto pair_to_minmax = cuda::make_transform_iterator(
cudf::dictionary::detail::make_dictionary_pair_iterator<T>(*d_dictionary, true),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, some exploration would be good here. This could first do a groupby-count to determine which keys are present (possibly including null values) and then do the minmax reduction over the keys with nonzero counts.

@davidwendt

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 0470761 into NVIDIA:main Jul 31, 2026
134 of 136 checks passed
@davidwendt
davidwendt deleted the dict-non-unique-keys branch July 31, 2026 13:57
@GregoryKimball GregoryKimball moved this from Slip to Landed in libcudf Aug 20, 2026
@GregoryKimball GregoryKimball removed this from libcudf Aug 21, 2026
@GregoryKimball GregoryKimball moved this from Slip to Landed in libcudf Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3 - Ready for Review Ready for review by team improvement Improvement / enhancement to an existing function libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change

Projects

Status: Landed

Development

Successfully merging this pull request may close these issues.

7 participants